﻿using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Sidvall.Data
{
	public static class TreeManager
	{
		#region Public Members

		public const string DefaultNameSeparator = "\\";

		#region FindChildPath

		public static T[] FindChildPath<T>(string childId, IEnumerable<T> allItems, T item) where T : IChildItem
		{
			List<T> pathItems;

			pathItems = new List<T>();
			if (!string.IsNullOrWhiteSpace(childId))
				PrepareChildPath(pathItems, childId, allItems, item);

			return pathItems.ToArray();
		}
		public static T[] FindChildPath<T>(string childId, IEnumerable<T> allItems, IEnumerable<T> items) where T : IChildItem
		{
			List<T> pathItems;

			pathItems = new List<T>();
			if (!string.IsNullOrWhiteSpace(childId))
				PrepareChildPath(pathItems, childId, allItems, items);

			return pathItems.ToArray();
		}

		#endregion
		#region FindParentPath

		public static T[] FindParentPath<T>(string parentId, IEnumerable<T> allItems, T item) where T : IChildItem
		{
			List<T> pathItems;

			pathItems = new List<T>();
			PrepareParentPath(pathItems, parentId, allItems, item);

			return pathItems.ToArray();
		}

		#endregion
		#region GetChildPath

		public static string GetChildPath(string childId, IEnumerable<IChildItem> allItems, IChildItem item)
		{
			return GetChildPath(childId, allItems, item, DefaultNameSeparator);
		}
		public static string GetChildPath(string childId, IEnumerable<IChildItem> allItems, IChildItem item, string separator)
		{
			IChildItem[] items;

			items = FindChildPath(childId, allItems, item);

			return JoinToString(items, separator, false);
		}
		public static string GetChildPath(string childId, IEnumerable<IChildItem> allItems, IEnumerable<IChildItem> items)
		{
			return GetChildPath(childId, allItems, items, DefaultNameSeparator);
		}
		public static string GetChildPath(string childId, IEnumerable<IChildItem> allItems, IEnumerable<IChildItem> items, string separator)
		{
			items = FindChildPath(childId, allItems, items);

			return JoinToString(items, separator, false);
		}

		#endregion
		#region GetRootPath

		public static string GetRootPath(IChildItem item, IEnumerable<IChildItem> allItems)
		{
			return GetRootPath(item, allItems, DefaultNameSeparator);
		}
		public static string GetRootPath(IChildItem item, IEnumerable<IChildItem> allItems, string separator)
		{
			IChildItem[] treeItems;

			treeItems = FindParentPath(null, allItems, item);

			return JoinToString(treeItems, separator, false);
		}

		#endregion
		#region JoinToString

		public static string JoinToString(IEnumerable<IItem> items, string separator, bool reversed)
		{
			System.Text.StringBuilder path;

			path = new System.Text.StringBuilder();
			if (items != null)
			{
				foreach (var item in items)
				{
					if (reversed)
					{
						if (path.Length > 0)
							path.Insert(0, separator);
						path.Insert(0, item.Name);
					}
					else
					{
						if (path.Length > 0)
							path.Append(separator);
						path.Append(item.Name);
					}
				}
			}
			return path.ToString();
		}

		#endregion
		#region MoveAsync

		public static async Task<TreeItemMoveResult> MoveAsync(string parentId, int displayPosition, ITreeItemManager manager, IChildItem item)
		{
			TreeItemMoveResult result;
			int pos;
			bool newParent;

			if (manager == null)
				throw new System.ArgumentNullException("manager");

			result = new TreeItemMoveResult()
			{
				Item = item
			};

			// Get oldSiblings and newSiblings
			result.OldSiblings = await GetChildItemsAsync(manager, item.ParentId, item.Id).ConfigureAwait(false);
			if (string.IsNullOrWhiteSpace(parentId))
			{
				if (string.IsNullOrWhiteSpace(item.ParentId))
				{
					result.NewSiblings = result.OldSiblings;
					newParent = false;
				}
				else
				{
					result.NewSiblings = await GetChildItemsAsync(manager, null, item.Id).ConfigureAwait(false);
					newParent = true;
				}
			}
			else
			{
				if (string.IsNullOrWhiteSpace(item.ParentId))
				{
					result.NewSiblings = await GetChildItemsAsync(manager, parentId, item.Id).ConfigureAwait(false);
					newParent = true;
				}
				else if (item.ParentId == parentId)
				{
					result.NewSiblings = result.OldSiblings;
					newParent = false;
				}
				else
				{
					result.NewSiblings = await GetChildItemsAsync(manager, parentId, item.Id).ConfigureAwait(false);
					newParent = true;
				}
			}

			// Update oldSiblings.DisplayPosition
			pos = 1;
			if (result.OldSiblings != null)
			{
				foreach (var sibling in result.OldSiblings)
				{
					sibling.DisplayPosition = pos;
					pos += 1;
				}
			}

			// Update newSiblings.DisplayPosition
			pos = 1;
			if (result.NewSiblings != null)
			{
				foreach (var sibling in result.NewSiblings)
				{
					if (pos == displayPosition)
						pos += 1;
					sibling.DisplayPosition = pos;
					pos += 1;
				}
			}
			if (!newParent)
				result.NewSiblings = null;

			// Update item
			if (!string.IsNullOrWhiteSpace(parentId))
				item.ParentId = parentId;
			else
				item.ParentId = null;
			item.DisplayPosition = displayPosition;

			return result;
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "displayPosition+1")]
		public static async Task<TreeItemMoveResultCollection> MoveAsync(string parentId, int displayPosition, ITreeItemManager manager, string[] itemIds)
		{
			TreeItemMoveResultCollection result;
			TreeItemMoveResult resultItem;
			IChildItem item;

			if (itemIds == null)
				return null;
			if (manager == null)
				throw new System.ArgumentNullException("manager");

			result = new TreeItemMoveResultCollection();
			foreach (var id in itemIds)
			{
				item = await GetItemAsync(manager, id).ConfigureAwait(false);
				if (item == null)
					continue;
				resultItem = await MoveAsync(parentId, displayPosition, manager, item).ConfigureAwait(false);
				result.Add(resultItem);
				displayPosition += 1;
			}
			return result;
		}

		#endregion

		#endregion
		#region Private Members

		#region GetChildItemsAsync

		private static async Task<IEnumerable<IChildItem>> GetChildItemsAsync(ITreeItemManager manager, string parentId, params string[] invalidIds)
		{
			HashSet<string> index;
			List<IChildItem> items;
			IEnumerable source;
			IChildItem item;

			index = new HashSet<string>(invalidIds);
			items = new List<IChildItem>();
			source = await manager.GetChildrenAsync(parentId).ConfigureAwait(false);
			if (source != null)
			{
				foreach (var sourceItem in source)
				{
					item = sourceItem as IChildItem;
					if (item != null)
					{
						if (!index.Contains(item.Id))
							items.Add(item);
					}
				}
			}
			return (from o in items
					orderby o.DisplayPosition
					select o);
		}

		#endregion
		#region GetItemAsync

		private static async Task<IChildItem> GetItemAsync(ITreeItemManager manager, string id)
		{
			object item;

			item = await manager.GetItemAsync(id).ConfigureAwait(false);

			return item as IChildItem;
		}

		#endregion
		#region PrepareChildPath

		private static bool PrepareChildPath<T>(List<T> pathItems, string childId, IEnumerable<T> allItems, IEnumerable<T> items) where T : IChildItem
		{
			if (items == null)
				return false;
			foreach (var item in items)
			{
				if (PrepareChildPath(pathItems, childId, allItems, item))
					return true;
			}
			return false;
		}
		private static bool PrepareChildPath<T>(List<T> pathItems, string childId, IEnumerable<T> allItems, T item) where T : IChildItem
		{
			IEnumerable<T> items;
			bool result;

			if (item.Id == childId)
				result = true;
			else
			{
				items = (from o in allItems
						 where o.ParentId == item.Id
						 select o);
				result = PrepareChildPath(pathItems, childId, allItems, items);
			}
			if (result)
				pathItems.Insert(0, item);
			return result;
		}

		#endregion
		#region PrepareParentPath

		private static void PrepareParentPath<T>(List<T> pathItems, string parentId, IEnumerable<T> allItems, T item) where T : IChildItem
		{
			T parent;

			pathItems.Insert(0, item);
			parent = (from o in allItems
					  where o.Id == item.ParentId
					  select o).FirstOrDefault();
			if ((parent == null) || (parent.Id == parentId))
				return;
			PrepareParentPath(pathItems, parentId, allItems, parent);
		}

		#endregion

		#endregion
	}
}
